/**
 * Copyright 2009-2010 - YangJiandong(chunquedong)
 * 
 * This file is part of ChunMap project
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE(Version >=3)
 * 你可以自由复制、传播本项目的下载包文件，但必须保持其完整性。
 * 我们不用对使用中的风险及由此造成的损失承担任何责任。
 * 详细情况请见《ChunMap许可协议》。

 * 想了解更多有关ChunMap的信息，请访问http://code.google.com/p/chunmap/
 */
package chunmap.model.relate;

import chunmap.model.elem.Envelope;
import chunmap.model.geom.*;
import static chunmap.model.relate.IntersectionMatrix.*;

/**
 * 计算两个几何体之间的关系
 * 
 * @author chunquedong
 * 
 */
public abstract class ComputeIm {

	protected Geometry g1;
	protected Geometry g2;
	protected IntersectionMatrix im;
	private boolean isReverse = false;

	/**
	 * 返回IM
	 * 
	 * @return
	 */
	public IntersectionMatrix getIM() {
		if (im == null) {
			if (!g1.isValid() || !g2.isValid()) {
				throw new GeometryException();
			}
			im = new IntersectionMatrix();
			im.set(Outer, Outer, AreaDim);
			computeIM();
			// 转置
			if (isReverse) {
				im = im.reverse();
			}
		}
		return im;
	}

	/**
	 * 对调两个几何体顺序的IM
	 * 
	 * @return
	 */
	protected ComputeIm setReverse(boolean b) {
		isReverse = b;
		return this;
	}

	// ------------------------------------------------------------主函数

	/**
	 * 计算IM的模板，主要方法。次方法也可能被子类覆盖以提高效率
	 */
	protected void computeIM() {
		Envelope en1 = g1.getEnvelop();
		boolean hasInte = en1.hasIntersect(g2.getEnvelop());
		if (!hasInte) {
			// 相离时
			disjoin();
		} else {
			// 设置边界
			setBorderIM();
			// 设置内部
			int iiDim = inner2innerDim(g1, g2);
			im.set(Inner, Inner, iiDim);
			// 设置内外，外内
			setInnerOuterAndOuterInner();
		}
	}

	// ------------------------------------------------------------辅助函数

	/**
	 * 设置内外，外内
	 * 
	 */
	private void setInnerOuterAndOuterInner() {
		int dim1 = g1.getGeometryType().dimension();
		int dim2 = g2.getGeometryType().dimension();

		if (dim1 < dim2) {
			setInnerOuter();
			im.set(Outer, Inner, dim2);
		} else if (dim1 > dim2) {
			im.set(Inner, Outer, dim1);
			setOuterInner();
		} else {
			setInnerOuter();
			setOuterInner();
		}
	}

	private void setInnerOuter() {
		if (isDisjoin()) {
			im.set(Inner, Outer, getBoundaryDimension(g1));
		} else if (within(g1, g2)) {
			im.set(Inner, Outer, EmptyDim);
		} else {
			im.set(Inner, Outer, getBoundaryDimension(g1));
		}
	}

	/**
	 * @return
	 */
	private boolean isDisjoin() {
		return im.get(Inner, Inner) == EmptyDim
				&& im.get(Inner, Border) == EmptyDim;
	}

	private void setOuterInner() {
		if (isDisjoin()) {
			im.set(Inner, Outer, getBoundaryDimension(g1));
		} else if (within(g2, g1)) {
			im.set(Outer, Inner, EmptyDim);
		} else {
			im.set(Outer, Inner, getBoundaryDimension(g2));
		}
	}

	// ------------------------------------------------------------钩子方法
	// 
	/**
	 * 内部和内部的交集维度。计算时可参考边界交集。
	 * 
	 * @param g1
	 * @param g2
	 * @return
	 */
	protected abstract int inner2innerDim(Geometry g1, Geometry g2);

	// 
	/**
	 * 是否完全被包含在里面。计算时可参考IIDim,但相同维度计算不可使用边界交集。
	 * 
	 * @param g1
	 * @param g2
	 * @return
	 */
	protected abstract boolean within(Geometry g1, Geometry g2);

	// ------------------------------------------------------------相离时

	/**
	 * 相离时设置IM
	 */
	private void disjoin() {
		im.set(Outer, Inner, g2.getGeometryType().dimension());
		im.set(Inner, Outer, g1.getGeometryType().dimension());

		int bdim1 = getBoundaryDimension(g1);
		int bdim2 = getBoundaryDimension(g2);

		im.set(Outer, Border, bdim2);
		im.set(Border, Outer, bdim1);
	}

	/**
	 * 边界维度
	 * 
	 * @return
	 */
	private int getBoundaryDimension(Geometry g) {
		Geometry b1 = g.getBoundary();
		int bdim1 = (b1 == null) ? -1 : b1.getGeometryType().dimension();
		return bdim1;
	}

	// ------------------------------------------------------------设置边界

	/**
	 * 设置边界处的IM
	 */
	private void setBorderIM() {
		Geometry b2 = g2.getBoundary();
		if (b2 != null) {
			IntersectionMatrix tim = ComputeImFactory.getInstance()
					.getImComputer(g1, b2).getIM();
			im.set(Inner, Border, getDim(tim, Inner));
			im.set(Border, Border, getDim(tim, Border));
			im.set(Outer, Border, getDim(tim, Outer));
		}

		Geometry b1 = g1.getBoundary();
		if (b1 != null) {
			IntersectionMatrix tim = ComputeImFactory.getInstance()
					.getImComputer(b1, g2).getIM();
			im.set(Border, Inner, getDim2(tim, Inner));
			im.set(Border, Border, getDim2(tim, Border));
			im.set(Border, Outer, getDim2(tim, Outer));
		}
	}

	private int getDim(IntersectionMatrix tim, int i) {
		int ii = tim.get(i, Inner);
		int ib = tim.get(i, Border);

		return Math.max(ii, ib);
	}

	private int getDim2(IntersectionMatrix tim, int i) {
		int ii = tim.get(Inner, i);
		int ib = tim.get(Border, i);

		return Math.max(ii, ib);
	}

}
